package tech.waterism.algorithm.moea.PSO;

import tech.waterism.constant.StringConstants;
import tech.waterism.dto.*;
import tech.waterism.entity.DStRsvrfsr;
import tech.waterism.enums.CatalogEnum;
import tech.waterism.enums.FieldEnum;
import tech.waterism.enums.LogicEnum;
import tech.waterism.enums.ValueTypeEnum;
import tech.waterism.utils.CommonFun;
import tech.waterism.utils.ConvertZV;
import tech.waterism.utils.ForecastDispatchUtils;
import tech.waterism.utils.method.ResFlood;
import tech.waterism.utils.method.StatisticsCalculate;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.*;

public class CheckUtil {
    private static final Double MAX = 10000d;
    private static final Double MAX_QOUT = 1000000d;

    public static Map<String, Object> obj2Map(Object obj) throws Exception {
        Map<String, Object> map = new HashMap<>();
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            map.put(field.getName(), field.get(obj));
        }
        return map;
    }

    public static Map<String, String> obj2StringMap(Object obj) throws Exception {
        Map<String, String> map = new HashMap<>();
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.get(obj) != null) {
                map.put(field.getName(), field.get(obj).toString());
            }
        }
        return map;
    }

    public static void setData(Object obj, String _field, Object _value) throws Exception {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (_field.equals(field.getName())) {
                field.setAccessible(true);
                field.set(obj, _value);
                return;
            }
        }
    }

    public static Double getValue(LogFieldDto logFieldDto, DispatchBound bound, FloodCharacterDto floodCharacter,
            ForecastDataDto data) throws Exception {
        return getValue(logFieldDto, bound, floodCharacter, data, null, 0, 0);
    }

    public static Double getValue(LogFieldDto logFieldDto, DispatchBound bound, FloodCharacterDto floodCharacter,
            ForecastDataDto data, FPIndexDto fpIndex, int timeNum, int timeNumOperate) throws Exception {
        ValueTypeEnum valueType = bound.getValueType();
        return switch (valueType) {
            case FIXED -> bound.getValue();
            case CHARACTER -> getCharacterValue(logFieldDto, bound.getCatalog(), floodCharacter, data, fpIndex, timeNum,
                    timeNumOperate);
            case MAX_VALUE -> Double.MAX_VALUE;
            case MIN_VALUE -> 0d;
            default -> throw new Exception("未知的值类型");
        };
    }

    public static Double getCharacterValue(LogFieldDto logFieldDto, CatalogEnum catalog,
            FloodCharacterDto floodCharacter, ForecastDataDto data) throws Exception {
        return getCharacterValue(logFieldDto, catalog, floodCharacter, data, null, 0, 0);
    }

    public static Double getCharacterValue(LogFieldDto logFieldDto, CatalogEnum catalog,
            FloodCharacterDto floodCharacter, ForecastDataDto data, FPIndexDto fpIndex, int timeNum, int timeNumOperate)
            throws Exception {
        try {
            if (CatalogEnum.getByType(StringConstants.RSVR).contains(catalog)) {
                if (catalog.equals(CatalogEnum.FSLTDZ) && floodCharacter.getStRsvrfsrBList() != null &&
                        floodCharacter.getStRsvrfsrBList().size() > 0) {
                    SimpleDateFormat sf = new SimpleDateFormat("MMdd");
                    String dtStr = sf.format(data.getDt());
                    int bg, ed, dt;
                    for (DStRsvrfsr dStRsvrfsr : floodCharacter.getStRsvrfsrBList()) {
                        if (dStRsvrfsr.getBgmd() != null && dStRsvrfsr.getEdmd() != null) {
                            bg = Integer.parseInt(dStRsvrfsr.getBgmd());
                            ed = Integer.parseInt(dStRsvrfsr.getEdmd());
                            dt = Integer.parseInt(dtStr);
                            if (dt >= bg && dt <= ed) {
                                return (double) CheckUtil.obj2Map(dStRsvrfsr).get(catalog.getField());
                            }
                        }
                    }
                } else if (floodCharacter.getStRsvrfcchB() != null) {
                    return (double) CheckUtil.obj2Map(floodCharacter.getStRsvrfcchB()).get(catalog.getField());
                }
            }
            if (CatalogEnum.getByType("riverZ").contains(catalog) ||
                    CatalogEnum.getByType("riverQ").contains(catalog)) {
                StRvfcchBDto stRvfcchB = floodCharacter.getStRvfcchB();
                if (stRvfcchB != null) {
                    return (double) CheckUtil.obj2Map(stRvfcchB).get(catalog.getField());
                }
            }
            if (catalog.equals(CatalogEnum.SW) && fpIndex != null && timeNum >= 1) {
                double qout =
                        ForecastDispatchUtils.getQoutMaxByGateOpenAll(fpIndex, fpIndex.getData().get(timeNum), timeNum);
                return qout;
            }
            if (catalog.equals(CatalogEnum.INFLOW_MAX) && fpIndex != null && timeNum >= 1) {
                Date fymdh = fpIndex.getData().get(timeNumOperate).getDt();
                double qpeak = StatisticsCalculate.getQpeak(fpIndex.getData(), fymdh);
                return qpeak;
            }
        } catch (Exception e) {
            throw new Exception(
                    logFieldDto.getStnm() + "(" + logFieldDto.getStcd() + ")" + "变量" + catalog.getField() +
                            "未找到对应的值");
        }
        throw new Exception(logFieldDto.getStnm() + "(" + logFieldDto.getStcd() + ")" + "变量" + catalog.getField() +
                "没有适配比较");
    }

    /**
     * @param constrainValue 约束值
     * @param value          实测值
     * @param logic          逻辑
     */
    public static boolean checkLogic(Double constrainValue, Double value, LogicEnum logic) {
        return switch (logic) {
            case GT -> value - constrainValue > 0;
            case LT -> value - constrainValue < 0;
            case GTE -> value - constrainValue >= 0;
            case LTE -> value - constrainValue <= 0;
            case EQ, AEQ -> value - constrainValue == 0;
            default -> false;
        };
    }

    /**
     * @param constrainValue 约束值
     * @param value          实测值
     * @param logic          逻辑
     */
    public static double getConstraintDiff(Double constrainValue, Double value, LogicEnum logic) {
        return switch (logic) {
            case GT -> value - constrainValue > 0 ? 0 : Math.abs(value - constrainValue);
            case LT -> value - constrainValue < 0 ? 0 : Math.abs(value - constrainValue);
            case GTE -> value - constrainValue >= 0 ? 0 : Math.abs(value - constrainValue);
            case LTE -> value - constrainValue <= 0 ? 0 : Math.abs(value - constrainValue);
            case AEQ, EQ -> Math.abs(value - constrainValue);
            default -> MAX;
        };
    }

    public static Double calFitness(FPIndexDto index, Date fymdh, DispatchObjective objective) throws Exception {
        List<Double> resultList = new LinkedList<>();
        if (objective.getCompareField().equals(FieldEnum.POWER_E)) {
            resultList.add(index.getEPower() == null ? 0d : index.getEPower());
        } else {
            List<ForecastDataDto> data = index.getData();
            for (ForecastDataDto datum : data) {
                if (datum.getDt().after(fymdh)) {
                    resultList.add((double) CheckUtil.obj2Map(datum).get(objective.getCompareField().getField()));
                }
            }
        }
        double sum = 0;
        switch (objective.getObjective()) {
            case "+":
                for (Double aDouble : resultList) {
                    sum += Math.abs(MAX_QOUT - aDouble);
                }
                break;
            case "-":
                sum = resultList.stream().mapToDouble(Double::doubleValue).sum();
                break;
            default:
                return null;
        }
        return sum;
    }

    public static Double calFitnessCombined(List<FPIndexDto> indexDtos, Date fymdh, DispatchObjective objective)
            throws Exception {
        List<Double> resultList = new LinkedList<>();
        if (objective.getCompareField().equals(FieldEnum.POWER_E)) {
            for (FPIndexDto indexDto : indexDtos) {
                resultList.add(indexDto.getEPower() == null ? 0d : indexDto.getEPower());
            }
        } else {
            for (FPIndexDto index : indexDtos) {
                List<ForecastDataDto> data = index.getData();
                for (ForecastDataDto datum : data) {
                    if (datum.getDt().after(fymdh)) {
                        resultList.add((double) CheckUtil.obj2Map(datum).get(objective.getCompareField().getField()));
                    }
                }
            }
        }
        double sum = 0;
        // todo change logic when field is not only power
        switch (objective.getObjective()) {
            case "+":
                sum = Math.abs(MAX_QOUT - resultList.stream().mapToDouble(Double::doubleValue).sum());
                break;
            case "-":
                sum = resultList.stream().mapToDouble(Double::doubleValue).sum();
                break;
            default:
                return null;
        }
        return sum;
    }

    /**
     * 检查水位约束，如果不满足，做水量平衡
     */
    public static boolean checkRz(List<TopoDataDto> topoDataDtos, List<DispatchConstraint> constrains)
            throws Exception {
        TopoDataDto topoDataDto = topoDataDtos.get(0);
        Date fymdh = topoDataDto.getFymdh();
        Map<String, FPIndexDto> map = new HashMap<>();
        for (TopoObjectDto topoObjectDto : topoDataDto.getTopoObject()) {
            map.put(topoObjectDto.getFpIndex().getStcd(), topoObjectDto.getFpIndex());
        }
        List<DispatchConstraint> constraintList =
                constrains.stream().filter(i -> i.getCompareField().equals(FieldEnum.RZ)).toList();
        boolean changed = false;
        for (DispatchConstraint constraint : constraintList) {
            String stcd = constraint.getStcd();
            if (!map.containsKey(stcd)) {
                continue;
            }
            FPIndexDto index = map.get(stcd);
            List<ForecastDataDto> data = index.getData();
            LogFieldDto logFieldDto = new LogFieldDto().setStcd(stcd).setStnm(index.getStnm());
            for (int timeNum = 0; timeNum < data.size(); timeNum++) {
                ForecastDataDto datum = data.get(timeNum);
                if (!datum.getDt().after(fymdh)) {
                    continue;
                }
                CheckValueDto newRz =
                        CommonFitness.getConstrainValue(logFieldDto, datum, constraint, index.getFloodCharacter());
                if (!newRz.getChanged()) {
                    continue;
                }
                changed = true;
                datum.setRz(newRz.getValue());
                CurveZVDto zvDto = ConvertZV.getVFromZ(index.getCurveHs(), datum.getRz());
                datum.setV(zvDto.getResult());
                datum.setQOut(ResFlood.waterBalanceByWaterLevel(index.getData().get(timeNum - 1).getV(), datum.getV(),
                        datum.getQIn(), CommonFun.multiKFromFlowToVolumn(index.getClen(), index.getUnt())));
            }
        }
        return changed;
    }
}
